NESten 0.6 Mapper SDK readme.				08:15 24.04.00

Written by TNSe

------------
Introduction
------------

This text file is meant to help you make Mapper plugins for NESten 0.6.


---------------
Version History
---------------

0.6  F MapperVersion 1.1
-------------------------
 - Added SetCHR_RAM_xxxx_xxxx(), needed for Mapper85, Konami VRC7

0.6  D MapperVersion 1.0
-------------------------
 - First revision

--------------------
How to make a mapper
--------------------

First of all you need to know how to make a DLL in your programming
language of choice. As my favourite language is Delphi, I will show
how to do it in that. But Jabo has been nice enough to make a
mapper2 example in VC++.

The first thing you need to do in Delphi, is to make a new DLL
project. It is recommended that you open the Mapperbase.dpr
included in this ZIP file.

Inside Mapperbase.dpr everything is set up to be easily made
into a mapper.

Const
 ThisMapperInfo:TMapperInfo = (
  MapperNum : 2;
  VersionLo : 0;
  VersionHi : 1;
  BankSize  : 16384;
  Name      : 'UNROM';
  Author    : 'TNSe');

First of all change the "MapperNum" field to the number of the mapper
you are writing, let's say 2.
VersionLo and VersionHi is the internal versions/revision of the Mapper,
(NESten does not use this for anything).
BankSize describes what the minimum bankswitch size is. For Mapper2,
you can only switch 16k PRG ROM at a time, so set that to 16384.
(This is very important in NESten, since the recompiling core
 generates code aligned to this size)
Name is the Name of the mapper, for Mapper2, that is UNROM.
Author is your name (and @mail etc)

After filling out that, you can start on the required Exported
functions.

Required functions are:

 LoadMapper   - Tells Mapper which interface Version NESten will give to it.
                LoadMapper will return a PMapperInfo pointer. (The above
                record you just filled out) If the version of NESten is
                incorrect vs the mapper, it should return NIL (NULL)

 UnloadMapper - If you use any GetMem (or malloc etc) use UnloadMapper to
                free up the memory used.

 InitMapper   - This is called when NESten resets emulation. Inside here
                you must set up Write/Read handlers, Correct Bank setup and
                Mirroring. NESten passes a record of variables and functions
                which the mapper must use to set banks/handlers.

 SaveMI       - This is called when an user wants to save his current game.
                The mapper must then write its internal variables to a
                passed 128 byte array.

 LoadMI       - This is called when an user wants to load a game.
                The mapper must then set its internal variables correctly.

Optional functions:

 HBlank       - This function is called after each emulated scanline.
                Normally used for triggering IRQs. Returns 0 if
                IRQ is not triggered, and 1 if IRQ should be triggered.
                First parameter is the Scanline NESten is currently at,
                Second is the contents of the byte at $2001.
 
 TileHandler  - This function is called for each scanline of a tile.
                So far only used in Mapper9 and Mapper10.
                Returns 0 if no CHR banks were changed, and 1 if any
                CHR banks were changed.
                First parameter is which pattern table it is using.
                (0 = $0000..$0FFF, 1 = $1000..$1FFF)
                Second parameter is which tile it is rendering (0-255)

First thing we need to do is write the code behind LoadMapper and UnloadMapper.

Function  LoadMapper(VersionNeeded:LongInt):PMapperInfo; cDecl;
begin
 If (VersionNeeded > CurrentMapperInterface) then
  begin
   MessageBox(0,'DLL is not compatible with the current mapper interface!','Mapper -1',0);
   Result := Nil;
  end
 else Result := @ThisMapperInfo;
end;

Notice here that I check if NEStens version is larger than the internal version,
and therefore I give a messagebox and return Nil as result, else I return
a pointer to the MapperInfoStructure.

Procedure UnloadMapper; cDecl;
begin
 // Nothing 4 now.
end;

UnloadMapper does nothing in this example.

Now let's look at InitMapper.

Procedure InitMapper(Const MapperParam:TMapperParam); cDecl;
begin
 // Copy the passed parameters to get access to the functions
 MP := MapperParam;

Here we take backup of the Parameters passed to InitMapper.
MapperParam contains a whole bunch of variables and functions we
will look at later.

 For X := $8 to $F do
  MP.SetWriteHandler(X,PRG_Write);

Since writes to $8000..$FFFF on Mapper2 makes the mapper change which
PRG-ROM pages are swapped in, we must trap these writes. We do that via
Installing a write handler on $8,$9,...,$F.
Code inside the WriteHandler is shown below.

 MP.SetPRG_89AB(0);
 MP.SetPRG_CDEF(-1);

Now we set the correct banksetup of Mapper2. $8 starts with
the first 16k PRG-ROM in the ROM. First = 0.
$C starts with the last 16k PRG-ROM in the ROM. Since NESten
automatically wraps any values written to the SetPRG's you
don't have to worry about unused bits (usually :)

 If (MP.HasCHR_ROM <> 0) then
  MP.SetCHR_0000_1FFF(0)
 else
  MP.SetCHR_RAM(0);

Here we check if this ROM contains any CHR-ROM. If it does, we
set up the PPU to use the first 8k of CHR-ROM (via the
MP.SetCHR_0000_1FFF(0) ). If the ROM doesn't contain any CHR-ROM,
the ROM must have CHR-RAM.

 if ((MP.Flag1 AND 1) <> 0) then
  MP.Mirror_V else MP.Mirror_H;

end;

And at last we set up the mirroring.

Now let us take a look at our Write Handler....

Procedure PRG_Write(Bank:LongInt; Where:LongInt; What:LongInt); cDecl;
begin
 MP.SetPRG_89AB(What);
end;

Parameter definition:
Bank   -> The upper 4 bits of the address written to.
Where  -> The lower 12 bits of the address written to.
What   -> The byte written to that address.

So if a game writes $67 to $8765, Bank would be $8, Where = $765
and What = $67.

 MP.SetPRG_89AB(What); sets the first 16k at $8 to the byte written.

------------------------------
The big TMapperParam structure
------------------------------

 Flag1       - The first of the two flag bytes in the .NES header.
               Contains mirroring bit, SRAM bit, Trainer bit, 4 screen bit
               and the lower 4 bits of the mapper #

 Flag2       - The second byte, which contains the upper 4 bits of the mapper #.

 HasCHR_ROM  - if 0, then there is no CHR-ROM, if 1, there is.

 Unused1     - Unused as of now.

 SetWriteHandler,
 GetWriteHandler  - These two functions respectively Sets and Gets a WriteHandler.
                    The first parameter on SetWriteHandler is the Bank (upper 4
                    bits of the address) to install the write handler.
                    The second parameter is the Write Handler to install.
                    The parameter of GetWriteHandler is which Bank you want the
                    write handler from.

 SetReadHandler,
 GetReadHandler   - Same functionality as Set/GetWriteHandler, but Sets/Gets a
                    Readhandler for the specified bank instead.

 SetPRG_67,
 SetPRG_89,
 SetPRG_AB,
 SetPRG_CD,
 SetPRG_EF        - Sets an 8k PRG bank to the specified Bank. Notice that
                    it sets an entire 8k bank.

 SetPRG_89AB,
 SetPRG_CDEF      - Sets an 16k PRG Bank to the specified Bank.

 SetPRG_89ABCDEF  - Sets the entire 32k area from $8000..$FFFF to the specified bank.

 SetPRG_Custom    - Sets a bank at parameter1 to parameter2 with parameter3 size.
                    SetPRG_Custom(8,0,8192) sets bank 8 to PRG-ROM bank 0 with
                    a granularity of 8k. Notice that ONLY bank8 is set!
 
 GetPRG_Custom    - Almost same as SetPRG_Custom, but returns which PRG-ROM bank
                    is at parameter1 with parameter2 granularity.

 SetCHR_0000_03FF,
 SetCHR_0400_07FF,
 SetCHR_0800_0BFF,
 SetCHR_0C00_0FFF,
 SetCHR_1000_13FF,
 SetCHR_1400_17FF,
 SetCHR_1800_1BFF,
 SetCHR_1C00_1FFF - Sets a 1k CHR-ROM bank in the PPU to the specified bank.

 SetCHR_0000_07FF,
 SetCHR_0800_0FFF,
 SetCHR_1000_17FF,
 SetCHR_1800_1FFF - Sets a 2k CHR-ROM bank in the PPU to the specified bank.

 SetCHR_0000_0FFF,
 SetCHR_1000_1FFF - Sets a 4k CHR-ROM bank in the PPU to the specified bank.

 SetCHR_0000_1FFF - Sets a 8k CHR-ROM bank in the PPU to the specified bank.

 SetCHR_Custom    - Works in the same way as SetPRG_Custom except it does it on
                    the PPU banks :)

 SetCHR_RAM_xxxx_xxxx
                  - Works as the SetCHR_xxxx_xxx, but assigns CHR-RAM instead.

 GetCHR_Custom    - Guess :)

 SetCHR_RAM       - Sets 8k CHR-RAM to the PPU. (Parameter range = 0 to 3)

 Mirror_H,
 Mirror_V,
 Mirror_4,
 Mirror_S0,
 Mirror_S1        - Sets mirroring. 
                    H = Horizontal, V = Vertical, 4 = fourscreen, 
                    S0 = Single low, S1 = Single high

 Mirror_Custom    - Sets mirroring depending on the 4 parameters.
                    Mirror_Custom(0,0,0,0) equals Mirror_S0.
                    Mirror_Custom(0,1,2,3) equals Mirror_4...

 VRC6Write        - First parameter is which register is written to.
                    0 = square 1, 1 = square 2, 3 = sawtooth
                    Second parameter is which port is written to.
                    Third param is the data written to it.

 DbgOut           - Writes a PChar (char *) message to the debug console.
                    (View->Info)

 
---------
Now what?
---------

Go have fun adding mapper support to NESten for your favourite game!

TNSe
      
